package at.fhooe.mcm12.java.clients;

import java.io.Serializable;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.HashMap;

import at.fhooe.mcm12.java.interfaces.ISensorListener;
import at.fhooe.mcm12.java.interfaces.RMIClientManager;
import at.fhooe.mcm12.java.interfaces.RMISensorControllerInterface;
import at.fhooe.mcm12.java.interfaces.RMISensorInterface;
import at.fhooe.mcm12.java.sensors.AbstractSensor;
import at.fhooe.mcm12.java.sensors.SensorController;
import at.fhooe.mcm12.java.sensors.data.SensorValue;

/**
 * @author Patrick Hutflesz, Michael Schoellhammer
 * 
 */
public class ClientManager extends UnicastRemoteObject implements
		ISensorListener, Serializable, RMIClientManager {

	private ArrayList<AbstractClient> m_enabledClients;
	private RMISensorControllerInterface m_sensorController;
	private HashMap<RMISensorInterface, ArrayList<AbstractClient>> m_sensorMap;

	/**
	 * Creates a new ClientManager instance.
	 */
	public ClientManager() throws RemoteException {
		initiateRMIServer();
		initiateRMIClient();

		m_enabledClients = new ArrayList<AbstractClient>();
		m_sensorMap = new HashMap<RMISensorInterface, ArrayList<AbstractClient>>();
	}

	private void initiateRMIServer() {
		try {

			if (System.getSecurityManager() == null) {
				System.setProperty("java.security.policy",
						"file:./res/rmi.policy");
				System.setSecurityManager(new SecurityManager());
			}

			UnicastRemoteObject.unexportObject(this, true);

			RMIClientManager stub = (RMIClientManager) UnicastRemoteObject
					.exportObject(this, 0);
			Registry registry = LocateRegistry
					.createRegistry(Registry.REGISTRY_PORT);

			registry.rebind(RMIClientManager.NAME, stub);
			System.out.println("Registered: " + RMIClientManager.NAME + " -> "
					+ this.getClass().getName() + "[" + this + "]");

		} catch (Exception _e) {
			_e.printStackTrace();
		}
	}

	private void initiateRMIClient() {
		System.setProperty("java.security.policy", "file:./res/rmi.policy");
		if (System.getSecurityManager() == null) {
			System.setSecurityManager(new SecurityManager());
		}

		try {
			Registry remoteRegistry = LocateRegistry.getRegistry(
					RMISensorControllerInterface.HOST, Registry.REGISTRY_PORT);
			m_sensorController = (RMISensorControllerInterface) remoteRegistry
					.lookup(RMISensorControllerInterface.NAME);
			 m_sensorController.addListener(this);
		} catch (RemoteException | NotBoundException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Getter for SensorController.
	 * 
	 * @return the RMI stub for the SensorController.
	 */
	@Override
	public RMISensorControllerInterface getSensorController() {
		return m_sensorController;
	}

	/**
	 * Enables the given {@link AbstractClient} instance so it will receive
	 * sensor updates.
	 * 
	 * @param _client
	 *            the client to disable
	 */
	@Override
	public void disable(AbstractClient _client) {
		if (_client != null && m_enabledClients.contains(_client)) {
			m_enabledClients.remove(_client);
		}
	}

	/**
	 * Enables the given {@link AbstractClient} instance so it will receive
	 * sensor updates.
	 * 
	 * @param _client
	 *            the client to enable
	 */
	@Override
	public void enable(AbstractClient _client) {
		if (_client != null && !m_enabledClients.contains(_client)) {
			m_enabledClients.add(_client);
		}
	}

	/**
	 * Note: this method must be included in the RMI interface!
	 * 
	 * @return the list of enabled clients.
	 */
	@Override
	public ArrayList<AbstractClient> getClients() {
		return m_enabledClients;
	}

	/**
	 * Note: this method must be included in the RMI interface!
	 * 
	 * @param _sensor
	 *            the sensor
	 * @return all clients that are registered for the given sensor instance, or
	 *         an empty list if no clients are registered.
	 */
	@Override
	public ArrayList<AbstractClient> getClientsForSensor(AbstractSensor _sensor) {
		ArrayList<AbstractClient> clients = null;
		synchronized (m_sensorMap) {
			clients = m_sensorMap.get(_sensor);
		}
		if (clients == null) {
			clients = new ArrayList<AbstractClient>();
		}
		return clients;
	}

	/**
	 * Note: this method must be included in the RMI interface!
	 * 
	 * @param _client
	 *            the client
	 * @return all sensors that are registered to the given client, or an empty
	 *         list if no sensors are registered.
	 */
	@Override
	public ArrayList<RMISensorInterface> getSensorsForClient(
			AbstractClient _client) {
		ArrayList<RMISensorInterface> sensors = new ArrayList<RMISensorInterface>();
		synchronized (m_sensorMap) {
			for (RMISensorInterface sensor : m_sensorMap.keySet()) {
				for (AbstractClient client : m_sensorMap.get(sensor)) {
					if (client.getName().equals(_client.getName())) {
						sensors.add(sensor);
					}
				}
			}
		}
		return sensors;
	}

	/**
	 * @return the list of sensors a client can register for, or an empty list
	 *         if there are no sensors available.
	 * @throws RemoteException
	 * 
	 * @see SensorController#getSubscribableSensors()
	 */
	@Override
	public ArrayList<RMISensorInterface> getSensors() throws RemoteException {
		return m_sensorController.getSubscribableSensors();
	}

	/**
	 * Note: this method must be included in the RMI interface!
	 * 
	 * @param _client
	 *            the client
	 * @return true if the given client is enabled, false otherwise.
	 */
	@Override
	public boolean isEnabled(AbstractClient _client) {
		return m_enabledClients.contains(_client);
	}

	/**
	 * Note: this method must be included in the RMI interface!
	 */
	@Override
	public void onSensorValueReceived(String _sensor, SensorValue _value) {
		System.out.println("Client Manager received sensor update");
		ArrayList<AbstractClient> clients = null;
		synchronized (m_sensorMap) {
			for (RMISensorInterface sensor : m_sensorMap.keySet()) {
				try {
					if(sensor.getName().equals(_sensor)){
						clients = m_sensorMap.get(sensor);
						break;
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}
		if (clients != null) {
			for (AbstractClient client : clients) {
				for (AbstractClient enabledClient : m_enabledClients) {
					if(enabledClient.getName().equals(client.getName())){
						try {
							client.onSensorValueReceived(_sensor, _value);
						} catch (RemoteException e) {
							e.printStackTrace();
						}
					}
				} 
			}
		}
	}

	/**
	 * Registers the given {@link AbstractClient client} for the specified
	 * {@link AbstractSensor sensor}. The client will afterwards receive updates
	 * whenever the ClientManager is notified. In case the client is already
	 * registered for the given sensor type the client will not be registered
	 * again, i.e. the client will never receive the same update twice or more
	 * often, no matter how many times he registered. Note that after
	 * registering for a new sensor, a call to
	 * {@link ClientManager#enable(AbstractClient) enable()} is still necessary
	 * to be notified of the sensor updates!
	 * 
	 * @param _client
	 *            the client instance to register
	 * @param _sensor
	 *            the sensor the client wants to register for
	 */
	@Override
	public void registerFor(AbstractClient _client, RMISensorInterface _sensor) {
		// The synchronized block may be a bit too big, but I think it's better
		// than several small blocks.
		synchronized (m_sensorMap) {
			ArrayList<AbstractClient> clients = m_sensorMap.get(_sensor);
			if (clients == null) {
				clients = new ArrayList<AbstractClient>();
			}
			if (!clients.contains(_client)) {
				clients.add(_client);
			}
			m_sensorMap.put(_sensor, clients);
		}
	}

	/**
	 * Removes the given {@link AbstractClient client} from the list of
	 * listeners for the specified {@link AbstractSensor sensor}.
	 * 
	 * @param _client
	 *            the client instance to unregister
	 * @param _sensor
	 *            the sensor to unregister
	 */
	@Override
	public void unregisterFor(AbstractClient _client, RMISensorInterface _sensor) {
		synchronized (m_sensorMap) {
			ArrayList<AbstractClient> clients = m_sensorMap.get(_sensor);
			if (clients != null) {
				int i;
				boolean found = false;
				for (i = 0; i < clients.size(); i++) {
					if (clients.get(i).getName().equals(_client.getName())) {
						found = true;
						break;
					}
				}
				if (found)
					clients.remove(i);
			}
			m_sensorMap.put(_sensor, clients);
		}
	}
	
	public static void main(String[] args) {
		
		try {
			new ClientManager();
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	
}
